#include "USBCommunication.h"
#include "libusb.h"

#if defined _WIN32
#include <Windows.h>
#include <Dbt.h>
#include <devguid.h>
// 具体的设备 GUID 需要 initguid, 如 usbiodef
#include <initguid.h>
// USB 设备
// GUID_DEVINTERFACE_USB_DEVICE
#include <usbiodef.h>
// HID 人机交互设备-鼠标键盘等
#include <hidclass.h>
// 键盘 GUID_DEVINTERFACE_KEYBOARD
#include <ntddkbd.h>
// 鼠标 GUID_DEVINTERFACE_MOUSE
#include <ntddmou.h>
#endif

#ifdef __APPLE__
#include "wx/wx.h"
#include <Security/Authorization.h>
#include <Security/AuthorizationTags.h>
#endif

#include <wx/log.h>

#define USE_ADMIN 0

USBCommunication::USBCommunication(uint16_t _vid, uint16_t _pid,int infNum)
{
    vid = _vid;
    pid = _pid;
    interface_number = infNum;
    usb_contex = nullptr;
    hDevice = nullptr;
    isConnected = false;
    kernelDriverDetached = 0;

    struct libusb_init_option options = {};
    // const struct libusb_init_option options = {
    //     .option = LIBUSB_OPTION_LOG_LEVEL,
    //     .value = {.ival = LIBUSB_LOG_LEVEL_DEBUG}
    // };
#if LIBUSB_API_VERSION >= 0x0100010A
    options.option = LIBUSB_OPTION_LOG_LEVEL;
    options.value.ival = LIBUSB_LOG_LEVEL_ERROR;
    int rc = libusb_init_context(&usb_contex,&options,1);
#else
    int rc = libusb_init(usb_contex);
#endif
    if(rc != LIBUSB_SUCCESS)
    {
    }
    // isRunning = true;
    // thread_libusb = std::thread(&USBCommunication::thread_run, this,usb_contex);
    // thread_libusb.detach();
}


USBCommunication::~USBCommunication()
{
    isRunning = false;
    // thread_libusb.join();
}


void USBCommunication::SetVidPid(uint16_t _vid, uint16_t _pid)
{
    vid = _vid;
    pid = _pid;
}

void USBCommunication::SetInterfaceNumber(int infNum)
{
    interface_number = infNum;
}

bool USBCommunication::Connect()
{
    if(isConnected == false)
    {
        wxLogInfo(wxString::Format("connect to device,vid:%04x,pid:%04x",vid,pid));
        int rc;
        // libusb_set_log_cb(usb_contex,myLibusbLog,NULL);
        kernelDriverDetached = 0;
        hDevice = libusb_open_device_with_vid_pid(usb_contex,vid,pid);
        if (!hDevice)
        {
            wxLogInfo("open device fail,");
            // libusb_exit(usb_contex);
            return false;
        }
#if (defined __APPLE__) && (USE_ADMIN)
        AuthorizationRef authorizationRef;
        AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
        AuthorizationRights rights = {1, &right};
        AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
        AuthorizationEnvironment environment = {0, NULL};

        // OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
        OSStatus status = AuthorizationCreate(&rights, kAuthorizationEmptyEnvironment, flags, &authorizationRef);
        if (status != errAuthorizationSuccess) {
            // 处理授权创建失败的情况
            wxLogError("获取授权失败.");
            return false;
        }

        status = AuthorizationCopyRights(authorizationRef, &rights, kAuthorizationEmptyEnvironment, flags, NULL);
        if (status != errAuthorizationSuccess) {
            // 处理授权复制失败的情况
            AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
            wxLogError("授权复制失败.");
            return false;
        }
#endif
    //#if ((defined __APPLE__) && (USE_ADMIN) || (defined _WIN32))
    #if (1)
        // 检查内核驱动是否连接到了这个设备，如果是，则从内核驱动断开它
        // mac上需要添加<key>com.apple.vm.device-access</key>权限才有效
        if (libusb_kernel_driver_active(hDevice, interface_number))
        {
            rc = libusb_detach_kernel_driver(hDevice, interface_number);
            if (rc == LIBUSB_SUCCESS)
            {
                kernelDriverDetached = 1;
            }
            else if(rc != LIBUSB_ERROR_NOT_SUPPORTED)
            {
                std::cout << "detaching kernel driver not supported" << ",libusb_error:" << libusb_strerror(libusb_error(rc)) << std::endl;
                libusb_close(hDevice);
                // libusb_exit(usb_contex);
                hDevice = nullptr;
#if (defined __APPLE__) && (USE_ADMIN)
                AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
#endif
                return false;
            }
        }
    #endif
#if (defined __APPLE__) && (USE_ADMIN)
    AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults);
#endif
        // 声明接口0
        rc = libusb_claim_interface(hDevice, interface_number);
        if (rc != LIBUSB_SUCCESS)
        {
            wxLogInfo(wxString::Format("claiming interface fialed,interface:%s,libusb_error:%s",interface_number, libusb_strerror(libusb_error(rc))));
            libusb_close(hDevice);
            // libusb_exit(usb_contex);
            hDevice = nullptr;
            return false;
        }
        isConnected = true;
        wxLogInfo("connected usb device success");
    }
    else
    {
        wxLogInfo("libusb is already connected");
    }
    return isConnected;
}

bool USBCommunication::Disconnect()
{
    if (isConnected)
    {
        // 释放接口0
        int rc = libusb_release_interface(hDevice, interface_number);
        if (LIBUSB_SUCCESS != rc)
        {
            wxLogInfo(wxString::Format("releasing interface fialed,%s",libusb_strerror(libusb_error(rc))));
        }
        //如果我们之前从接口＃0分离了内核驱动程序，我们现在就可以了需要再次附加它
        if (kernelDriverDetached)
        {
            libusb_attach_kernel_driver(hDevice, 0);
        }
        libusb_hotplug_deregister_callback(usb_contex,device_hotplug_handle);
        // 关闭 libusb
        libusb_release_interface(hDevice,interface_number);
        libusb_close(hDevice);
        // libusb_exit(usb_contex);
#if usb_contex != NULL
        usb_contex = nullptr;
#endif
        hDevice = nullptr;
        isConnected = false;
        wxLogInfo("libusb is closed");
    }
    else
    {
        wxLogInfo("libusb is not connect");
        return true;
    }
    return true;
}

bool USBCommunication::IsConnected()
{
    return isConnected;
}

bool USBCommunication::StartHotplugCheck()
{
    int rc = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG);
    if(rc)
    {
        rc = libusb_hotplug_register_callback(usb_contex,
                                              LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
                                              LIBUSB_HOTPLUG_NO_FLAGS,
                                              vid, pid,
                                              LIBUSB_HOTPLUG_MATCH_ANY,
                                              device_hotplug_callback,
                                              NULL,
                                              &device_hotplug_handle);
        if (LIBUSB_SUCCESS != rc)
        {
            wxLogInfo(wxString::Format("registering hotplug failed.%s",libusb_strerror(libusb_error(rc))));
            return false;
        }
        wxLogInfo(wxString::Format("register hotplug success,vid:%04x,pid:%04x",vid,pid));
        return true;
    }
    wxLogInfo("not supported or unimplemented on this platform ");
    return false;
}

int USBCommunication::device_hotplug_callback(struct libusb_context *context, struct libusb_device *dev, libusb_hotplug_event event, void *userdata)
{
    struct libusb_device_descriptor desc;

    (void)userdata;
    (void)context;

    libusb_get_device_descriptor(dev, &desc);
    if(event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
    {
        std::cout << "hotplug remove";
    }
    else if(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
    {
        std::cout << "hotplug arrived";
    }
    std::cout << " CLASS:";
    std::cout << std::hex << desc.bDeviceClass;
    std::cout << " SUBCLASS:";
    std::cout << std::hex << desc.bDeviceSubClass;
    std::cout << " iSerialNumber:";
    std::cout << std::hex << desc.iSerialNumber;
    std::cout << std::endl;

    return 0;
}

void USBCommunication::myLibusbLog(libusb_context *contex,enum libusb_log_level level,const char *msg)
{
    std::cout << "libusb log:" << msg;
}

void USBCommunication::thread_run(void *param)
{
    wxLogInfo("libusb thread run");
    int rc;
    libusb_context *ctx = (libusb_context *)param;
    timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 500;
    // isRunning = true;
    while (isRunning)
    {
        rc = libusb_handle_events_timeout_completed(ctx,&tv,NULL);
        if(rc != LIBUSB_SUCCESS)
        {
            wxLogInfo(wxString::Format("libusb_handle_events_timeout_completed failed.%s",libusb_strerror(libusb_error(rc))));
        }
    }
}

USBCommunication::Status USBCommunication::WriteData(const char *data, int len,unsigned char endpoint,int timeout)
{
    int rc;
    int wl,al;
    unsigned char *buff;

    if(!isConnected)
    {
        wxLogInfo("usb is not connect");
        return FAIL;
    }
    uint16_t maxSize = libusb_get_max_packet_size(libusb_get_device(hDevice),endpoint);
    buff = (unsigned char *)data;
    while(len)
    {
        wl = len > maxSize ? maxSize : len;
        rc = libusb_bulk_transfer(hDevice, endpoint & 0x7F, (unsigned char *)buff, wl, &al, timeout);
        if (rc == LIBUSB_SUCCESS)
        {
            len -= al;
            buff += al;
        }
        else
        {
            wxLogInfo(wxString::Format("libusb_bulk_transfer failed.%s",libusb_strerror(libusb_error(rc))));
            return FAIL;
        }
    }
    return SUCCESS;
}

USBCommunication::Status USBCommunication::ReadData(char *data, int length, int *actual_length,unsigned char endpoint,int timeout)
{
    int rc;
    int al;
    unsigned char *buff;

    if(!isConnected)
    {
        wxLogInfo("usb is not connect");
        return FAIL;
    }
    if(actual_length)
    {
        *actual_length = 0;
    }
    uint16_t maxSize = libusb_get_max_packet_size(libusb_get_device(hDevice),endpoint);
    buff = (unsigned char *)data;
    al = 0;
    rc = libusb_bulk_transfer(hDevice, endpoint | 0x80, (unsigned char *)buff, maxSize, &al, timeout);
    if (rc == LIBUSB_SUCCESS)
    {
        if(actual_length)
        {
            *actual_length = al;
        }
        return SUCCESS;
    }
    else
    {
        wxLogInfo(wxString::Format("libusb_bulk_transfer failed.%s",libusb_strerror(libusb_error(rc))));
        return FAIL;
    }
}
